﻿///<FileHeader>
///     <FileName>Extensions.cs</FileName>
///     <Author>Kiran Chand Palakkattiri</Author>
///     <Project>
///         <Name>Property Visibility Extensions for DSL</Name>
///         <Url>http://kiranchand.codeplex.com</Url>
///     </Project>
///     <ChangeSet id="1006" date="April 08, 2010."/>
///     <Contents>
///         <Content type="class" name="Extensions"
///             comments="Static class internal to PVE10 with utility extension methods."/>
///     </Contents>
///     <Dependencies>
///         <Dependency type="reference" namespace="System" />
///         <Dependency type="reference" namespace="System.Collections" />
///         <Dependency type="reference" namespace="System.ComponentModel" />
///         <Dependency type="reference" namespace="System.Reflection" />
///         <Dependency type="reference" namespace="System.Linq" />
///         <Dependency type="reference" namespace="Microsoft.VisualStudio.Modeling.Design"
///             assembly="Microsoft.VisualStudio.Modeling.Sdk" version="9.0.0.0" prerequisites="Visual Studio SDK 1.1" />
///     </Dependencies>
///     <Modifications>
///         <Modification changeset="1007" date="April 08, 2010" author="Kiran Chand Palakkattiri">
///             <Target="function" targetname="GetFieldInfo(...)" type="create"
///                 comment="added function to reflect the field information"/>
///             <Target="function" targetname="GetFieldValue(...)" type="update"
///                 comment="added the support for getting the field value of the base class;
///                 optimized the code."/>
///             <Target="function" targetname="SetFieldValue(...)" type="update"
///                 comment="added the support for setting the field value of the base class;
///                 optimized the code."/>
///             <Target="function" targetname="GetFields(...)" type="delete"
///                 comment="removed this, since a new function, GetFieldInfo(...) is created
///                 supports values including baseclass types."/>
///         </Modification>
///         <Modification changeset="1008" date="April 26, 2010" author="Kiran Chand Palakkattiri">
///             <Target="function" targetname="Merge(...)" type="create"
///                 comment="added function to merge the set operation."/>
///         </Modification>
///     </Modifications>
///     <Version>3.0.0.0</Version>
///</FileHeader>

using System;
using System.Collections;
using System.ComponentModel;
using System.Reflection;
using Microsoft.VisualStudio.Modeling.Design;
using System.Collections.Generic;

namespace Kiranchand.Dsl.Extensions.PropertyVisibility
{
    /// <summary>
    /// Static class internal to PVE10 with utility extension methods
    /// </summary>
    internal static class Extensions
    {
        /// <summary>
        /// Iterates the enumerable and performs the given pericate
        /// </summary>
        /// <param name="enumerable">instance of the IEnumerable</param>
        /// <param name="pericate">pericate to be performed</param>
        internal static void ForEach(this IEnumerable enumerable, Action<object> action)
        {
            // null check
            if (enumerable != null && action != null)
            {
                // enumerator
                IEnumerator enumerator = null;

                try
                {
                    // get enumerator
                    enumerator = enumerable.GetEnumerator();
                }
                catch (Exception)
                {
                    // eat!
                }

                // null check
                if (enumerator != null)
                {
                    // move on
                    while (enumerator.MoveNext())
                    {
                        // get the current
                        object current = enumerator.Current;
                        // perform pericate
                        action.Invoke(current);
                    }
                }
            }
        }

        /// <summary>
        /// Iterates an enumerable and returns the first object matches to the given predicate.
        /// </summary>
        /// <param name="enumerable">instance of IEnumerable</param>
        /// <param name="predicate">the predicate to check the match</param>
        /// <returns>the object matches the predicte.</returns>
        internal static object Find(this IEnumerable enumerable, Predicate<object> predicate)
        {
            // return value
            object returnValue = null;

            // null check
            if (enumerable != null && predicate != null)
            {
                // enumerator
                IEnumerator enumerator = null;

                try
                {
                    // try get
                    enumerator = enumerable.GetEnumerator();
                }
                catch (Exception)
                {
                    // eat!
                }

                // null check
                if (enumerator != null)
                {
                    // enumerate
                    while (enumerator.MoveNext())
                    {
                        // get current
                        object current = enumerator.Current;
                        // invoke predicate
                        if (predicate.Invoke(current))
                        {
                            // return current, if match
                            returnValue = current;
                            // break
                            break;
                        }
                    }
                }
            }

            // return
            return returnValue;
        }

        /// <summary>
        /// Get the standard value provider from the property descriptor
        /// </summary>
        /// <param name="descriptor">instance of the element property descriptor</param>
        /// <returns>standard value provider.</returns>
        internal static IStandardValueProvider GetStandardValueProvider(this PropertyDescriptor descriptor)
        {
            // return
            return (descriptor != null
                ? descriptor.GetFieldValue("element")
                : null) as IStandardValueProvider;
        }

        /// <summary>
        /// Gets the first field information from the type matching the given name.
        /// </summary>
        /// <param name="type">the type of the object.</param>
        /// <param name="fieldName">name of the field as string.</param>
        /// <returns>the field information if available or null</returns>
        internal static FieldInfo GetFieldInfo(this Type type, string fieldName)
        {
            // field information
            FieldInfo field = null;

            // check the field name!
            if (!string.IsNullOrEmpty(fieldName))
            {
                // iteration
                // type should not be null.
                // field is not found.
                while (type != null
                    && field == null)
                {
                    // get the fields
                    FieldInfo[] fields = type != null
                        ? type.GetFields(System.Reflection.BindingFlags.NonPublic
                        | System.Reflection.BindingFlags.Public
                        | System.Reflection.BindingFlags.Instance
                        | System.Reflection.BindingFlags.Static)
                        : new FieldInfo[] { };

                    // find the field info
                    field = fields.Find
                    (
                        // item as object ;)
                        item =>
                        {
                            // predicate
                            bool isMatch = false;

                            // current field
                            FieldInfo fieldInfo = item as FieldInfo;

                            // match the field name!
                            if (fieldInfo.Name.Equals(fieldName))
                            {
                                // update match
                                isMatch = true;
                            }

                            // return to predicate
                            return isMatch;
                        }
                    ) as FieldInfo;

                    // assigns the basetype!
                    type = type.BaseType;

                    // continue!
                }
            }

            // return
            return field;
        }

        /// <summary>
        /// Get the value of the field of the given instance with the given name.
        /// </summary>
        /// <param name="instance">instance of the object.</param>
        /// <param name="fieldName">name of the field.</param>
        /// <returns>value of the field as object.</returns>
        internal static object GetFieldValue(this object instance, string fieldName)
        {
            // get the field information
            FieldInfo field = instance != null
                ? instance.GetType().GetFieldInfo(fieldName)
                : null;

            // get the value
            return field != null
                && instance != null
                ? field.GetValue(instance) : null;
        }

        /// <summary>
        /// Sets the given value to the field of the given object with given name,
        /// if the type is assignable and the current value is different.
        /// </summary>
        /// <param name="instance">instance of the object.</param>
        /// <param name="fieldName">name of the field.</param>
        /// <param name="value">value to be set as object.</param>
        /// <returns>returns the status.</returns>
        internal static bool SetFieldValue(this object instance, string fieldName, object value)
        {
            // return status
            bool status = false;

            // get the field
            FieldInfo field = instance != null
                ? instance.GetType().GetFieldInfo(fieldName)
                : null;

            // null check
            if (field != null
                && instance != null)
            {
                // value is null
                if (value == null)
                {
                    // null can not be assigned to value type!
                    if (!field.FieldType.IsValueType)
                    {
                        // set value
                        field.SetValue(instance, value);

                        // set status
                        status = true;
                    }
                }
                // value is assignable?
                // current value  and the proposed value are equal?
                else if (field.FieldType.IsAssignableFrom(value.GetType())
                    && !value.Equals(field.GetValue(instance)))
                {
                    // set the value
                    field.SetValue(instance, value);

                    // set the status
                    status = true;
                }
            }

            // return the status
            return status;

        }

        /// <summary>
        /// Merges the given set operation with the original set individualOperation.
        /// </summary>
        /// <param name="current">first set operation</param>
        /// <param name="operation">second set operation</param>
        /// <returns>the combined set operation</returns>
        internal static SetOperation Merge(this SetOperation current, SetOperation operation)
        {
            // return the combined result
            return SetOperation.Intersection.Equals(current)
                || SetOperation.Intersection.Equals(operation)
                ? SetOperation.Intersection
                : current & operation;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="type"></param>
        /// <param name="flags"></param>
        /// <param name="matchCondition"></param>
        /// <param name="searchBaseClass"></param>
        /// <returns></returns>
        internal static IEnumerable<MemberInfo> GetMembers(this Type type
            , BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public
            , Predicate<MemberInfo> matchCondition = null
            , bool searchBaseClass = false)
        {

            while (type != null)
            {
                IList<MemberInfo> members = type.GetMembers(flags);
                for (int i = 0; i < members.Count; i++)
                {
                    MemberInfo member = members[i];
                    if (matchCondition == null
                        || matchCondition.Invoke(member))
                    {
                        yield return member;
                    }
                }

                if (type != typeof(object)
                    && type != type.BaseType
                    && searchBaseClass)
                {
                    type = type.BaseType;
                }
                else
                {
                    type = null;
                }
            }

        }

        internal static TResult Execute<TResult>(this Type type, object instance, string methodName, params object[] parameters)
        {
            TResult returnValue = default(TResult);

            if (type != null
                && !string.IsNullOrWhiteSpace(methodName))
            {
                BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Public;

                flags |= instance != null ? BindingFlags.Instance : BindingFlags.Static;

                foreach (MethodInfo method in type.GetMembers(matchCondition: item => item != null
                    && item.MemberType == MemberTypes.Method
                    && item.Name.Equals(methodName)
                    , searchBaseClass: true
                    , flags: flags))
                {
                    returnValue = (TResult)method.Invoke(instance, parameters);
                    break;
                }

            }

            return returnValue;
        }

    }
}
